#pragma once
#include "tgaimage.h"
#include "geometry.h"
#include "model.h"

extern const TGAColor white;
extern const TGAColor red;
extern const TGAColor green;

enum class triangleMode{ LINW_SWEEPING, INTERECTION };
enum class meshType{ LINE, TRIANGLE };

inline void line(int x0, int y0, int x1, int y1, TGAImage& image, TGAColor color)
{
    bool is_steep = false;
    if(std::abs(x1-x0)<std::abs(y1-y0))
    {
        std::swap(x0, y0);
        std::swap(x1, y1);
        is_steep = true;
    }
    if(x0 > x1)
    {
        std::swap(x0, x1);
        std::swap(y0, y1);
    }
    const int dx = x1 - x0;
    const int abs_k = std::abs(y1-y0) * 2;
    int error = 0.0;
    int y = y0;
    const int step = y0<y1 ? 1 : -1; 
    for(int x=x0; x<=x1; x++)
    {
        if(is_steep)
            image.set(y, x, color);
        else
            image.set(x, y, color);
        error += abs_k;
        if(error > dx)
        {
            y += step;
            error -= dx*2;
        }
    }
}

inline void line(Vec2i v0, Vec2i v1, TGAImage& image, TGAColor color)
{
    line(v0.x, v0.y, v1.x, v1.y, image, color);
}

inline Vec3f barycentric(Vec2i *pts, Vec2i P) { 
    Vec3f u = Vec3f(pts[2].x-pts[0].x, pts[1].x-pts[0].x, pts[0].x-P.x)^Vec3f(pts[2].y-pts[0].y, pts[1].y-pts[0].y, pts[0].y-P.y);
    /* `pts` and `P` has integer value as coordinates
       so `abs(u[2])` < 1 means `u[2]` is 0, that means
       triangle is degenerate, in this case return something with negative coordinates */
    if (std::abs(u.z)<1) return Vec3f(-1,1,1);
    return Vec3f(1.f-(u.x+u.y)/u.z, u.x/u.z, u.y/u.z); 
} 

inline void triangle(Vec2i t0, Vec2i t1, Vec2i t2, TGAImage& image, TGAColor color, triangleMode mode=triangleMode::INTERECTION)
{
    if(mode == triangleMode::LINW_SWEEPING)
    {
        if(t0.y > t1.y) std::swap(t0, t1);
        if(t0.y > t2.y) std::swap(t0, t2);
        if(t1.y > t2.y) std::swap(t1, t2);
        int total_height = t2.y-t0.y; 
        for (int y=t0.y; y<=t1.y; y++) 
        { 
            int segment_height = t1.y-t0.y+1; 
            float alpha = (float)(y-t0.y)/total_height; 
            float beta  = (float)(y-t0.y)/segment_height; // be careful with divisions by zero 
            Vec2i A = t0 + (t2-t0)*alpha; 
            Vec2i B = t0 + (t1-t0)*beta; 
            if(A.x > B.x)   std::swap(A, B);
            for(int i=A.x; i<=B.x; i++)
                image.set(i, y, color);
        } 
        for (int y=t1.y; y<=t2.y; y++) 
        { 
            int segment_height = t2.y-t1.y+1; 
            float alpha = (float)(y-t0.y)/total_height; 
            float beta  = (float)(y-t1.y)/segment_height; // be careful with divisions by zero 
            Vec2i A = t0 + (t2-t0)*alpha; 
            Vec2i B = t1 + (t2-t1)*beta; 
            if(A.x > B.x)   std::swap(A, B);
            for(int i=A.x; i<=B.x; i++)
                image.set(i, y, color);
        } 
    }
    else if(mode == triangleMode::INTERECTION)
    {
        Vec2i box_min(image.width()-1, image.height()-1); 
        Vec2i box_max(0, 0);
        Vec2i ts[3] = {t0, t1, t2};
        box_min.x = std::max(0, std::min(t0.x, std::min(t1.x, t2.x)));
        box_min.y = std::max(0, std::min(t0.y, std::min(t1.y, t2.y)));
        box_max.x = std::min(image.width()-1, std::max(t0.x, std::max(t1.x, t2.x)));
        box_max.y = std::min(image.height()-1, std::max(t0.y, std::max(t1.y, t2.y)));
        for(int x=box_min.x; x<=box_max.x; x++)
            for(int y=box_min.y; y<=box_max.y; y++)
            {
                Vec3f bc_screen = barycentric(ts, Vec2i(x, y));
                if(bc_screen.x<0 || bc_screen.y<0 || bc_screen.z<0)
                    continue;
                image.set(x, y, color);
            }
    }
}


inline void draw(const Model& model, TGAImage& image, TGAColor color, int width=800, int height=800, meshType type=meshType::TRIANGLE)
{
    Vec3f light_dir(0, 0, -1);
    if(type == meshType::LINE)
    {
        for (int i=0; i<model.nfaces(); i++) 
        { 
            std::vector<int> face = model.face(i); 
            for (int j=0; j<3; j++) 
            { 
                Vec3f v0 = model.vert(face[j]); 
                Vec3f v1 = model.vert(face[(j+1)%3]); 
                int x0 = (v0.x+1.)*width/2.; 
                int y0 = (v0.y+1.)*height/2.; 
                int x1 = (v1.x+1.)*width/2.; 
                int y1 = (v1.y+1.)*height/2.; 
                line(x0, y0, x1, y1, image, color); 
            } 
        }
    }
    else if(type == meshType::TRIANGLE)
    {
        TGAColor tmpColor(color);
        for(int i=0; i<model.nfaces(); i++)
        {
            std::vector<int> face = model.face(i);
            Vec3f v0 = model.vert(face[0]); 
            Vec3f v1 = model.vert(face[1]); 
            Vec3f v2 = model.vert(face[2]); 
            v0.x = (v0.x+1.)*width/2.; 
            v0.y = (v0.y+1.)*height/2.; 
            v1.x = (v1.x+1.)*width/2.; 
            v1.y = (v1.y+1.)*height/2.; 
            v2.x = (v2.x+1.)*width/2.; 
            v2.y = (v2.y+1.)*height/2.; 
            Vec3f normal = ((model.vert(face[2])-model.vert(face[0]))^(model.vert(face[1])-model.vert(face[0]))).normalize();
            float cos_a = normal * light_dir;
            std::cout << cos_a << std::endl;
            if(cos_a < 0)
                continue;
            else
            {
                tmpColor.bgra[0] = float(color.bgra[0])*cos_a; 
                tmpColor.bgra[1] = float(color.bgra[1])*cos_a; 
                tmpColor.bgra[2] = float(color.bgra[2])*cos_a; 
            }
            
            // triangle(Vec2i(v0.x, v0.y), Vec2i(v1.x, v1.y), Vec2i(v2.x, v2.y), image, TGAColor{uint8_t(rand()%255), uint8_t(rand()%255), uint8_t(rand()%255), 255});
            triangle(Vec2i(v0.x, v0.y), Vec2i(v1.x, v1.y), Vec2i(v2.x, v2.y), image, tmpColor);
        }
    }
} 